/*
 * Copyright 2025 LinQingYing. and contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of this source code is governed by the Apache License 2.0,
 * which allows users to freely use, modify, and distribute the code,
 * provided they adhere to the terms of the license.
 *
 * The software is provided "as-is", and the authors are not responsible for
 * any damages or issues arising from its use.
 *
 */

package org.cangnova.cangjie.ide.stubindex

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.NavigatablePsiElement
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.stubs.StubIndex
import com.intellij.psi.stubs.StubIndexKey
import com.intellij.util.CommonProcessors
import com.intellij.util.Processor
import com.intellij.util.Processors
import com.intellij.util.SmartList
import com.intellij.util.indexing.IdFilter
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
import kotlin.time.TimeSource

private val isNestedIndexAccessEnabled: Boolean by lazy { Registry.`is`("cangjie.indices.nested.access.enabled") }

abstract class CangJieStringStubIndexHelper<Key : NavigatablePsiElement>(private val valueClass: Class<Key>) {

    private val logger = Logger.getInstance(this.javaClass)
    abstract val indexKey: StubIndexKey<String, Key>
    operator fun get(
        fqName: String,
        project: Project,
        scope: GlobalSearchScope = GlobalSearchScope.allScope(project)
    ): Collection<Key> {
        return getByKeyAndMeasure(indexKey, logger) {
//            try {
                StubIndex.getElements(indexKey, fqName, project, scope, valueClass)
//            } catch (e: Exception) {
//                logger.error(e)
//                emptyList()
//            }
        }
    }

    fun getAllKeys(project: Project): Collection<String> {
        return getAllKeysAndMeasure(indexKey, logger) {
            StubIndex.getInstance().getAllKeys(indexKey, project)
        }
    }

    fun getAllElements(s: String, project: Project, scope: GlobalSearchScope, filter: (Key) -> Boolean): List<Key> {
        val values = SmartList<Key>()
        processElements(s, project, scope, null, CancelableCollectFilterProcessor(values, filter))
        return values
    }

    /**
     * Note: [processor] should not invoke any indices as it could lead to deadlock. Nested index access is forbidden.
     */
    fun processElements(s: String, project: Project, scope: GlobalSearchScope, processor: Processor<in Key>): Boolean {
        return processElements(s, project, scope, null, processor)
    }

    /**
     * Note: [processor] should not invoke any indices as it could lead to deadlock. Nested index access is forbidden.
     */
    fun processElements(
        s: String, project: Project, scope: GlobalSearchScope, idFilter: IdFilter? = null, processor: Processor<in Key>
    ): Boolean {
        return processElementsAndMeasure(indexKey, logger) {
            StubIndex.getInstance().processElements(indexKey, s, project, scope, idFilter, valueClass, processor)
        }
    }

    fun getAllElements(
        project: Project,
        scope: GlobalSearchScope,
        keyFilter: (String) -> Boolean = { true },
        valueFilter: (Key) -> Boolean
    ): List<Key> {
        val values = SmartList<Key>()
        processAllElements(project, scope, keyFilter, CancelableCollectFilterProcessor(values, valueFilter))
        return values
    }

    fun processAllElements(
        project: Project, scope: GlobalSearchScope, filter: (String) -> Boolean = { true }, processor: Processor<in Key>
    ) {
        val stubIndex = StubIndex.getInstance()

        if (isNestedIndexAccessEnabled) {
            stubIndex.processAllKeys(indexKey, project, CancelableDelegateFilterProcessor(filter) { key ->
                // process until the 1st negative result of processor
                stubIndex.processElements(indexKey, key, project, scope, valueClass, processor)
            })
        } else {
            // collect all keys, collect all values those fulfill filter into a single collection, process values after that

            val allKeys = HashSet<String>()
            val processAllKeys = processAllKeysAndMeasure(indexKey, logger) {
                stubIndex.processAllKeys(indexKey, project, CancelableCollectFilterProcessor(allKeys, filter))
            }
            if (!processAllKeys) return

            if (allKeys.isNotEmpty()) {
                val values = HashSet<Key>(allKeys.size)
                val collectProcessor = Processors.cancelableCollectProcessor(values)
                allKeys.forEach { s ->
                    val processElements = processElementsAndMeasure(indexKey, logger) {
                        stubIndex.processElements(indexKey, s, project, scope, valueClass, collectProcessor)
                    }
                    if (!processElements) return
                }
                // process until the 1st negative result of processor
                values.all(processor::process)
            }
        }
    }

    fun processAllKeys(scope: GlobalSearchScope, filter: IdFilter? = null, processor: Processor<in String>): Boolean {
        return processAllKeysAndMeasure(indexKey, logger) {
            StubIndex.getInstance().processAllKeys(indexKey, processor, scope, filter)
        }
    }

    fun processAllKeys(project: Project, processor: Processor<in String>): Boolean {
        return processAllKeysAndMeasure(indexKey, logger) {
            StubIndex.getInstance().processAllKeys(indexKey, project, processor)
        }
    }
}



class CancelableDelegateFilterProcessor<T>(
    private val filter: (T) -> Boolean, private val delegate: Processor<T>
) : Processor<T> {
    override fun process(t: T): Boolean {
        ProgressManager.checkCanceled()
        return if (filter(t)) {
            delegate.process(t)
        } else {
            true
        }
    }

    companion object {
        val ALWAYS_TRUE: (Any) -> Boolean = { true }
    }
}

class CancelableCollectFilterProcessor<T>(
    collection: Collection<T>, private val filter: (T) -> Boolean
) : CommonProcessors.CollectProcessor<T>(collection) {
    override fun process(t: T): Boolean {
        ProgressManager.checkCanceled()
        return super.process(t)
    }

    override fun accept(t: T): Boolean = filter(t)

    companion object {
        val ALWAYS_TRUE: (Any) -> Boolean = { true }
    }
}
